package org.fujene.memoryblock.blockwise;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.Constants;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;

public class BlockwiseDictionaryBlock extends MemoryBlock {
	private int opt_length = 0;
	private int opt_contents = 1;

	public BlockwiseDictionaryBlock(int bodySize, int ID, boolean isAllocate) {
		super(bodySize, ID, Constants.MEMORY_TYPE_DICT, isAllocate);
	}

	private class Dictionary {
		LookupPointer nextPtr;
		int docFreq;
		LookupPointer[] invIndices;
		int strlen;
		String str;

		// 読み込み用
		public Dictionary(int from, boolean readStr) {
			this.nextPtr = ByteConverter.getPtr(getBody(from, PTR));
			if (this.nextPtr.equals(Constants.emptyPointer))
				this.nextPtr = null;
			from += PTR;

			this.docFreq = ByteConverter.byteToInt(getBody(from, DOCFREQ));
			from += DOCFREQ;

			this.invIndices = new LookupPointer[opt_contents];
			for (int i = 0; i < this.invIndices.length; i++) {
				this.invIndices[i] = ByteConverter.getPtr(getBody(from, PTR));
				if (this.invIndices.equals(Constants.emptyPointer))
					this.invIndices[i] = null;
				from += PTR;
			}

			if (readStr) {
				this.strlen = ByteConverter.byteToInt(getBody(from, STRLEN));
				from += STRLEN;

				try {
					this.str = new String(getBody(from, strlen), "UTF-8");
				} catch (UnsupportedEncodingException e) {
				}
			}
		}

		public Dictionary(LookupPointer ptr, String str) {
			if (ptr != null)
				this.nextPtr = ptr;
			else
				this.nextPtr = Constants.emptyPointer;

			this.docFreq = 1;

			this.invIndices = new LookupPointer[opt_contents];
			for (int i = 0; i < this.invIndices.length; i++) {
				this.invIndices[i] = Constants.emptyPointer;
			}

			this.str = str;

			try {
				this.strlen = str.getBytes("UTF-8").length;
			} catch (UnsupportedEncodingException e) {
			}
		}

		public void setPtr(LookupPointer ptr, int index, boolean isIncrement) {
			if (index < opt_contents)
				this.invIndices[index] = ptr;
			
			if(isIncrement)
				this.docFreq++;
		}

		public void write(int from, boolean writeStr) {
			if (this.nextPtr != null)
				setBody(ByteConverter.setPtr(this.nextPtr), from);
			else
				setBody(ByteConverter.setPtr(Constants.emptyPointer), from);
			from += PTR;

			setBody(ByteConverter.intToByte(this.docFreq, DOCFREQ), from);
			from += DOCFREQ;

			for (int i = 0; i < this.invIndices.length; i++) {
				if (this.invIndices[i] != null)
					setBody(ByteConverter.setPtr(this.invIndices[i]), from);
				else
					setBody(ByteConverter.setPtr(Constants.emptyPointer), from);
				from += PTR;
			}

			if (writeStr) {
				try {
					setBody(ByteConverter.intToByte(
							this.str.getBytes("UTF-8").length, STRLEN), from);
					from += STRLEN;

					setBody(this.str.getBytes("UTF-8"), from);
				} catch (UnsupportedEncodingException e) {
				}
			}
		}
	}

	// 情報読み込み
	public ExtendTerm getTerm(int ptr) {
		Dictionary dict = new Dictionary(ptr, true);

		return new ExtendTerm(dict.str, dict.nextPtr, null, new LookupPointer(
				this.getID(), ptr), -1, dict.invIndices, null, dict.docFreq);
	}

	// 情報書き込み
	public void setTerm(int ptr, LookupPointer nextPtr, String str) {
		Dictionary dict = new Dictionary(nextPtr, str);

		dict.write(ptr, true);

		opt_length++;
	}

	public LookupPointer getNextPointer(int ptr) {
		Dictionary ptredDic = new Dictionary(ptr, false);

		return ptredDic.nextPtr;
	}

	public LookupPointer getDocIDPtr(int ptr, int index) {
		Dictionary ptredDic = new Dictionary(ptr, false);

		return ptredDic.invIndices[index];
	}

	public void setDocIDptr(int ptr, int index, LookupPointer docIDptr, boolean isIncrement) {
		Dictionary dict = new Dictionary(ptr, false);

		dict.setPtr(docIDptr, index, isIncrement);

		dict.write(ptr, false);
	}

	public void setNextPointer(int ptr, LookupPointer next) {
		Dictionary dict = new Dictionary(ptr, false);

		dict.nextPtr = next;

		dict.write(ptr, false);
	}

	// サイズ確認つき
	public int getNewPointer(String str) {
		try {
			byte[] strb = str.getBytes("UTF-8");
			int returnPtr = this.bodySize - this.bodyVacancy;
			if (this.bodyVacancy >= PTR + DOCFREQ + (opt_contents * PTR)
					+ STRLEN + strb.length) {
				this.bodyVacancy -= PTR + DOCFREQ + (opt_contents * PTR)
				+ STRLEN + strb.length;
				return returnPtr;
			} else {
				return -1;
			}
		} catch (UnsupportedEncodingException e) {
			return -1;
		}
	}

	public int getTermCount() {
		return opt_length;
	}

	public static byte[] createByteArray(LookupPointer nextPtr,
			int contentLength, int docfreq, String str)
			throws UnsupportedEncodingException, IOException {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		stream.write(ByteConverter.setPtr(nextPtr));

		stream.write(ByteConverter.intToByte(docfreq, DOCFREQ));

		for (int i = 0; i < contentLength; i++) {
			stream.write(ByteConverter.setPtr(Constants.emptyPointer));
		}

		stream.write(ByteConverter.intToByte(str.getBytes("UTF-8").length,
				DOCFREQ));

		stream.write(str.getBytes("UTF-8"));

		return stream.toByteArray();
	}

	public byte[] getData() throws IOException {
		ByteArrayOutputStream stream = new ByteArrayOutputStream(
				this.bodySize + 8);

		stream.write(getOptionByteStream());
		stream.write(this.getBody(0, this.bodySize));

		return stream.toByteArray();
	}

	public void setData(byte[] array) {
		opt_length = ByteConverter.byteToInt(array, 0, 4);
		opt_contents = ByteConverter.byteToInt(array, 4, 4);

		setBody(array, 8, bodySize, 0);
	}

	@Override
	public int getOptionSize() {
		return 2;
	}

	@Override
	public byte[] getOptionByteStream() {
		ByteArrayOutputStream stream = new ByteArrayOutputStream(8);

		try {
			stream.write(ByteConverter.intToByte(opt_length, 4));
			stream.write(ByteConverter.intToByte(opt_contents, 4));
		} catch (IOException e) {
		}

		return stream.toByteArray();
	}

	@Override
	public void setOption(int option, int value) {
		switch (option) {
		case 0:
			opt_length = value;
			break;
		case 1:
			opt_contents = value;
			break;
		}
	}

	@Override
	public String getClassName() {
		return "Block-wise DictionaryBlock";
	}
}
